/*
 *  policy - Part of the Policy Server Application
 *  Copyright (C) 2009 Liam Lowey
 *  
 *      http://policyserver.turnitin.co.uk/
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); 
 *  you may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at 
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0 
 *  
 *  Unless required by applicable law or agreed to in writing, software 
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 *  See the License for the specific language governing permissions and 
 *  limitations under the License. 
 */

using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System;

namespace policyserver
{
  class SocketCommandQueue : CommandQueue, IDisposable
  {
    private Socket _listenPoint;
    private Socket _sendPoint;
    private EndPoint _destination;


    public SocketCommandQueue()
      : this( IPAddress.Any, 53457 )
    {
    }


    public SocketCommandQueue( IPAddress bind, int preferredPort )
      : base()
    {
      int nextPort = preferredPort;

      bool bound = false;
      for( int portTries = 0; portTries < 100 && !bound; ++portTries )
      {
        try
        {
          _listenPoint = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp );
          _listenPoint.Bind( new IPEndPoint( bind, nextPort ) );
          bound = true;
          break;
        }
        catch( SocketException )
        {
          nextPort = randomPortNumber();
        }
      }

      if( !bound )
      {
        throw new SocketException();
      }

      _sendPoint = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp );

      IPAddress theDestination = bind;
      if( theDestination == IPAddress.Any )
      {
        theDestination = IPAddress.Loopback;
      }

      _destination = new IPEndPoint( theDestination, nextPort );

      _sendPoint.Blocking = true;
      _listenPoint.Blocking = true;

      trace( "SocketCommandQueue() - Bound to {0} on port {1}.", theDestination, nextPort );
    }


    ~SocketCommandQueue()
    {
      try { _sendPoint.Shutdown( SocketShutdown.Both ); }
      catch { }
      try { _listenPoint.Shutdown( SocketShutdown.Both ); }
      catch { }
      try { _sendPoint.Close(); }
      catch { }
      try { _listenPoint.Close(); }
      catch { }
    }


    public void Dispose()
    {
      try { _sendPoint.Shutdown( SocketShutdown.Both ); }
      catch { }
      try { _listenPoint.Shutdown( SocketShutdown.Both ); }
      catch { }
      try { _sendPoint.Close(); }
      catch { }
      try { _listenPoint.Close(); }
      catch { }
    }


    public Socket socket
    {
      get { return _listenPoint; }
    }


    protected override void doPushExtra() 
    {
      try
      {
        _sendPoint.SendTo( new byte[] { 1 }, _destination );
      }
      catch( SocketException )
      {
      }
    }


    protected override void doPopExtra() 
    {
      try
      {
        if( _listenPoint.Poll( 1, SelectMode.SelectRead ) )
        {
          byte[] data = new byte[128];
          _listenPoint.Receive( data );
        }
      }
      catch( SocketException )
      {
      }
    }


    private static void trace( string format, params object[] args )
    {
      System.Console.WriteLine( format, args );
    }


    static private int randomPortNumber()
    {
      byte[] randoms = new byte[2];

      RandomNumberGenerator rng = RandomNumberGenerator.Create();

      rng.GetBytes( randoms );

      ushort randomShort = 0;

      randomShort |= (ushort)randoms[0];
      randomShort <<= 8;
      randomShort |= (ushort)randoms[1];

      double refactor = (1.0 / (double)ushort.MaxValue) * (double)randomShort;

      double newPortNumber = 1024.0 + (refactor * (ushort.MaxValue - 1024));

      return (int)Math.Floor( newPortNumber );
    }
  }
}
